﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;

using ConfigDashboard.Models;

namespace ConfigDashboard.Controllers
{
    /// <summary>
    /// Import settings
    /// </summary>
    public class SettingsImporter
    {
        /// <summary>
        /// Imports the CSV file.
        /// </summary>
        /// <param name="results">The results.</param>
        /// <param name="csvStream">The CSV stream.</param>
        public void ImportCsvFile(SpreadsheetImportResult results, Stream csvStream)
        {
            DataTable dt = LoadCsvFile(csvStream);

            ConfigSettingsEntities db = new ConfigSettingsEntities();

            InsertOrUpdateEnvironments(db, results, dt);
            InsertOrUpdateSettingNames(db, results, dt);

            List<ConfigEnvironment> configEnvironments = db.ConfigEnvironments.ToList();
            List<ConfigSettingName> configSettingNames = db.ConfigSettingNames.ToList();
            List<ConfigSettingValue> configSettingValues = db.ConfigSettingValues.ToList();

            DataRow envRow = dt.Rows[0];
            for (int rowIndex = 1; rowIndex < dt.Rows.Count; rowIndex++)
            {
                DataRow currentRow = dt.Rows[rowIndex];
                for (int colIndex = 1; colIndex < dt.Columns.Count; colIndex++)
                {
                    string settingName = (string)currentRow[0];
                    string envName = (string)envRow[colIndex];
                    string value = null;
                    if (!currentRow.IsNull(colIndex))
                        value = (string)currentRow[colIndex];

                    if (!string.IsNullOrEmpty(settingName) && !string.IsNullOrEmpty(envName) && !string.IsNullOrEmpty(value))
                    {
                        ConfigEnvironment configEnvironment = configEnvironments.FirstOrDefault(e => e.EnvironmentName.Equals(envName, StringComparison.OrdinalIgnoreCase));
                        ConfigSettingName configSettingName = configSettingNames.FirstOrDefault(n => n.SettingName.Equals(settingName, StringComparison.OrdinalIgnoreCase));
                        ConfigSettingValue configSettingValue = configSettingValues.FirstOrDefault(v => v.SettingID == configSettingName.SettingID && v.EnvironmentID == configEnvironment.EnvironmentID);

                        if (configSettingValue == null)
                        {
                            configSettingValue = new ConfigSettingValue();
                            configSettingValue.SettingValue = value;
                            configSettingValue.SettingID = configSettingName.SettingID;
                            configSettingValue.EnvironmentID = configEnvironment.EnvironmentID;

                            db.ConfigSettingValues.AddObject(configSettingValue);

                            results.Messages.Add(
                                        new SpreadsheetImportMessage(
                                            SpreadsheetImportMessageType.Information,
                                            string.Format("Added setting {0}", value))
                                    );

                            results.SettingsImportedSuccessfully++;
                        }
                    }
                }
            }

            db.SaveChanges();
        }


        /// <summary>
        /// Inserts the or update environments.
        /// </summary>
        /// <param name="db">The db.</param>
        /// <param name="results">The results.</param>
        /// <param name="dt">The dt.</param>
        private void InsertOrUpdateEnvironments(ConfigSettingsEntities db, SpreadsheetImportResult results, DataTable dt)
        {
            List<ConfigEnvironment> configEnvironments = db.ConfigEnvironments.ToList();

            DataRow envRow = dt.Rows[0];
            for (int colIndex = 1; colIndex < dt.Columns.Count; colIndex++)
            {
                string envName = null;
                if (!envRow.IsNull(colIndex))
                    envName = (string)envRow[colIndex];

                if (!string.IsNullOrEmpty(envName))
                {
                    ConfigEnvironment configEnvironment = configEnvironments.FirstOrDefault(e => e.EnvironmentName.Equals(envName, StringComparison.OrdinalIgnoreCase));
                    if (configEnvironment == null)
                    {
                        configEnvironment = new ConfigEnvironment();
                        configEnvironment.EnvironmentName = envName;
                        configEnvironment.IsDefault = envName.StartsWith("Default", StringComparison.OrdinalIgnoreCase);
                        configEnvironment.ViewOrder = colIndex * 10;

                        db.ConfigEnvironments.AddObject(configEnvironment);


                        results.Messages.Add(
                                    new SpreadsheetImportMessage(
                                        SpreadsheetImportMessageType.Information,
                                        string.Format("Added Environment {0}", envName))
                                );

                        results.EnvironmentsImportedSuccessfully++;
                    }
                }
            }
            db.SaveChanges();
        }


        /// <summary>
        /// Inserts the or update setting names.
        /// </summary>
        /// <param name="db">The db.</param>
        /// <param name="results">The results.</param>
        /// <param name="dt">The data table.</param>
        private void InsertOrUpdateSettingNames(ConfigSettingsEntities db, SpreadsheetImportResult results, DataTable dt)
        {
            List<ConfigSettingName> configSettingNames = db.ConfigSettingNames.ToList();

            for (int rowIndex = 1; rowIndex < dt.Rows.Count; rowIndex++)
            {
                DataRow settingRow = dt.Rows[rowIndex];
                string settingName = null;
                if (!settingRow.IsNull(0))
                    settingName = (string)settingRow[0];

                if (!string.IsNullOrEmpty(settingName))
                {
                    ConfigSettingName configSettingName = configSettingNames.FirstOrDefault(n => n.SettingName.Equals(settingName, StringComparison.OrdinalIgnoreCase));
                    if (configSettingName == null)
                    {
                        configSettingName = new ConfigSettingName();
                        configSettingName.SettingName = settingName;
                        configSettingName.ViewOrder = rowIndex * 10;
                        db.ConfigSettingNames.AddObject(configSettingName);

                        results.Messages.Add(
                                    new SpreadsheetImportMessage(
                                        SpreadsheetImportMessageType.Information,
                                        string.Format("Added Setting Name {0}", settingName))
                                );

                        results.SettingNamesImportedSuccessfully++;
                    }
                }
            }
            db.SaveChanges();
        }


        /// <summary>
        /// Loads the CSV file.
        /// </summary>
        /// <param name="csvStream">The CSV stream.</param>
        /// <returns></returns>
        private DataTable LoadCsvFile(Stream csvStream)
        {
            DataTable dt = new DataTable("Settings");

            using (StreamReader reader = new StreamReader(csvStream))
            {
                CsvFileReader csvReader = new CsvFileReader(reader, "#");

                bool hasHeaderRowBeenEncountered = false;
                List<string> row = csvReader.ReadRow();
                while (null != row)
                {
                    if (!hasHeaderRowBeenEncountered)
                    {
                        // skip blank rows
                        if (row.Count > 0)
                        {
                            // Add a column for each cell in header row
                            for (int index = 0; index < row.Count; index++)
                            {
                                dt.Columns.Add(index.ToString(), typeof(string));
                            }
                            hasHeaderRowBeenEncountered = true;
                        }
                    }

                    if (hasHeaderRowBeenEncountered)
                    {
                        DataRow newRow = dt.NewRow();
                        for (int columnIndex = 0; columnIndex < row.Count; columnIndex++)
                        {
                            // this will throw out values in columns beyond the length of the header row
                            if (columnIndex < dt.Columns.Count)
                                newRow[columnIndex] = row[columnIndex];
                        }
                        dt.Rows.Add(newRow);
                    }

                    row = csvReader.ReadRow();
                }
            }
            return dt;
        }
    }


    /// <summary>
    /// Class to read data from a CSV file
    /// </summary>
    public class CsvFileReader
    {
        private TextReader _reader = null;
        private string _commentPrefix = null;

        /// <summary>
        /// Construct a CSV reader 
        /// </summary>
        /// <param name="reader">TextReader containing CSV</param>
        public CsvFileReader(TextReader reader)
        {
            _reader = reader;
        }

        /// <summary>
        /// Construct a CSV reader 
        /// </summary>
        /// <param name="reader">TextReader containing CSV</param>
        /// <param name="commentPrefix">lines prefixed with this string will be ignored</param>
        public CsvFileReader(TextReader reader, string commentPrefix)
            : this(reader)
        {
            _commentPrefix = commentPrefix;
        }

        /// <summary>
        /// Reads a row of data from a CSV file
        /// </summary>
        /// <returns></returns>
        public List<string> ReadRow()
        {
            List<string> row = null;

            string line = _reader.ReadLine();
            if (line != null) // at end of file
            {
                row = new List<string>();

                // comment line: ignore lines starting with comment symbol
                if (string.IsNullOrEmpty(_commentPrefix) || !line.StartsWith(_commentPrefix))
                {
                    int index = 0;
                    while (index < line.Length)
                    {
                        row.Add(GetValue(line, ref index));

                        // Advance to the next comma, or to the end, whichever comes first
                        while (index < line.Length && line[index] != ',')
                        {
                            index++;
                        }

                        index++;
                    }
                }
            }
            return row;
        }


        /// <summary>
        /// Get a single value at the specified offset index, includes
        /// special processing for quoted values according to the CSV
        /// convention
        /// </summary>
        /// <param name="line"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private string GetValue(string line, ref int index)
        {
            string value = null;

            if (line[index] != '"')
            {
                int start = index;
                while (index < line.Length && line[index] != ',')
                {
                    index++;
                }
                value = line.Substring(start, index - start);
            }
            else // if value is quoted
            {
                int start = ++index; // advance past quote
                while (index < line.Length)
                {
                    if (line[index] == '"')
                    {
                        // look ahead to next character
                        // if we are at the end of the line,
                        // or the next character isn't another quote
                        // then we are at the end of the quoted field
                        int nextChar = index + 1;
                        if (nextChar >= line.Length
                            || line[nextChar] != '"')
                        {
                            break;
                        }
                        else
                        {
                            index++;
                        }
                    }
                    index++;
                }
                value = line.Substring(start, index - start);

                // double quotes are replaced with single quote
                // as per the CSV convention
                value = value.Replace("\"\"", "\"");
            }

            return value;
        }
    }

}
